//
//  ChivoxLog.m
//  ChivoxEngine
//
//  Created by xianchen.peng on 2018/9/27.
//  Copyright © 2018 Chivox. All rights reserved.
//

#import "ChivoxLog.h"

#define _LOG_DEBUG 0
#define _LOG_INFO 1
#define _LOG_NOTICE 2
#define _LOG_WARN 3
#define _LOG_ERR 4

// 用于同步多线程互斥操作
@interface _LogFileSync : NSObject
@end
@implementation _LogFileSync
@end
static NSString *_logFileName = nil;

static int _logLevel = _LOG_NOTICE; // 缺省日志级别 NOTICE
static NSUncaughtExceptionHandler *_oriUncaughtExceptionHandler = NULL;

static void _log_file(NSString *fmt, ...) NS_FORMAT_FUNCTION(1,2);
static void _log_file(NSString *fmt, ...) {
    @synchronized([_LogFileSync class]) {
        if (_logFileName != nil) {
            va_list args;
            va_start(args, fmt);
            NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args];
            va_end(args);
            
            NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:_logFileName];
            if (fileHandle != nil) {
                [fileHandle seekToEndOfFile];
                NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
                [fileHandle writeData:data];
                [fileHandle writeData:[NSData dataWithBytes:"\n" length:1]];
                [fileHandle closeFile];
            }
        }
    }
}

static void _UncaughtExceptionHandler(NSException* exception) {
    NSString* name = [exception name];
    NSString* reason = [exception reason];
    NSArray* symbols = [exception callStackSymbols]; // 异常发生时的调用栈
    NSMutableString* strSymbols = [[NSMutableString alloc] init]; //将调用栈拼成输出日志的字符串
    for ( NSString* item in symbols ) {
        [ strSymbols appendString: item ];
        [ strSymbols appendString: @"\r\n" ];
    }
    
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]];
    [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    NSString *dateStr = [formatter stringFromDate:[NSDate date]];
    
    NSString *crashString = [NSString stringWithFormat:@"<- %@ ->[ Uncaught Exception ]\r\nName: %@, Reason: %@\r\n[ Fe Symbols Start ]\r\n%@[ Fe Symbols End ]\r\n\r\n", dateStr, name, reason, strSymbols];
    
    //NSLog(@"%@", crashString);
    _log_file(@"%@", crashString);
    
    if (_oriUncaughtExceptionHandler != NULL) {
        _oriUncaughtExceptionHandler(exception);
    }
}

void ChivoxLogInit(NSString *level) {
    if ([level isEqualToString:@"error"]) {
        _logLevel = _LOG_ERR;
    } else if ([level isEqualToString:@"warn"]) {
        _logLevel = _LOG_WARN;
    } else if([level isEqualToString:@"notice"]) {
        _logLevel = _LOG_NOTICE;
    } else if([level isEqualToString:@"info"]) {
        _logLevel = _LOG_INFO;
    } else if([level isEqualToString:@"debug"]) {
        _logLevel = _LOG_DEBUG;
    }
    
    if (_oriUncaughtExceptionHandler == NULL ||
        NSGetUncaughtExceptionHandler() != _UncaughtExceptionHandler) {
        _oriUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
    }
    NSSetUncaughtExceptionHandler(&_UncaughtExceptionHandler);
}

int ChivoxLogSetFile(NSString *pathName) {
    @synchronized([_LogFileSync class]) {
        if (_logFileName != nil) {
            NSLog(@"[error] [ChivoxLog] you shouldn't call ChivoxLogSetFile again.");
            return -1;
        }
        
        if (pathName == nil) {
            return -1;
        }
        
        pathName = [pathName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        if ([pathName isEqualToString:@""]) {
            NSLog(@"[error] [ChivoxLog] you shouldn't call ChivoxLogSetFile with empty pathName.");
            return -1;
        }
        
        NSFileManager *fileManager = [NSFileManager defaultManager];
        BOOL isDir = NO;
        if([fileManager fileExistsAtPath:pathName isDirectory:&isDir]) {
            if (isDir) {
                NSLog(@"[error] [ChivoxLog] you shouldn't call ChivoxLogSetFile with a directory.");
                return -1;
            }
            
            NSError *err = [[NSError alloc] init];
            if(NO == [fileManager removeItemAtPath:pathName error:&err]) {
                NSLog(@"[error] [ChivoxLog] cover old log-file failed. %@", [err localizedFailureReason]);
                return -1;
            }
        }
        
        BOOL ok = [fileManager createFileAtPath:pathName contents:nil attributes:nil];
        if (!ok) return -1;
        
        _logFileName = pathName;
        return 0;
    }
}



void ChivoxLogError(NSString *fmt, ...) {
    if (_logLevel <= _LOG_ERR) {
        va_list args;
        va_start(args, fmt);
        NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args];
        va_end(args);
        NSLog(@"[error] %@", msg);
        _log_file(@"[error] %@", msg);
    }
}

void ChivoxLogWarn(NSString *fmt, ...) {
    if (_logLevel <= _LOG_WARN) {
        va_list args;
        va_start(args, fmt);
        NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args];
        va_end(args);
        NSLog(@"[warn] %@", msg);
        _log_file(@"[warn] %@", msg);
    }
}

void ChivoxLogNotice(NSString *fmt, ...) {
    if (_logLevel <= _LOG_NOTICE) {
        va_list args;
        va_start(args, fmt);
        NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args];
        va_end(args);
        NSLog(@"[notice] %@", msg);
        _log_file(@"[notice] %@", msg);
    }
}

void ChivoxLogInfo(NSString *fmt, ...) {
    if (_logLevel <= _LOG_INFO) {
        va_list args;
        va_start(args, fmt);
        NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args];
        va_end(args);
        NSLog(@"[info] %@", msg);
        _log_file(@"[info] %@", msg);
    }
}

void ChivoxLogDebug(NSString *fmt, ...) {
    if (_logLevel <= _LOG_DEBUG) {
        va_list args;
        va_start(args, fmt);
        NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args];
        va_end(args);
        NSLog(@"[debug] %@", msg);
        _log_file(@"[debug] %@", msg);
    }
}

